// Copyright 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "content/renderer/web_ui_extension.h"

#include <memory>
#include <utility>

#include "base/strings/string_util.h"
#include "base/values.h"
#include "content/common/view_messages.h"
#include "content/public/child/v8_value_converter.h"
#include "content/public/common/bindings_policy.h"
#include "content/public/common/url_constants.h"
#include "content/public/renderer/chrome_object_extensions_utils.h"
#include "content/public/renderer/render_thread.h"
#include "content/public/renderer/render_view.h"
#include "content/renderer/web_ui_extension_data.h"
#include "gin/arguments.h"
#include "gin/function_template.h"
#include "third_party/WebKit/public/web/WebDocument.h"
#include "third_party/WebKit/public/web/WebKit.h"
#include "third_party/WebKit/public/web/WebLocalFrame.h"
#include "third_party/WebKit/public/web/WebUserGestureIndicator.h"
#include "third_party/WebKit/public/web/WebView.h"
#include "url/gurl.h"
#include "v8/include/v8.h"

namespace content {

namespace {

    bool ShouldRespondToRequest(
        blink::WebFrame** frame_ptr,
        RenderView** render_view_ptr)
    {
        blink::WebFrame* frame = blink::WebLocalFrame::frameForCurrentContext();
        if (!frame || !frame->view())
            return false;

        RenderView* render_view = RenderView::FromWebView(frame->view());
        if (!render_view)
            return false;

        GURL frame_url = frame->document().url();

        bool webui_enabled = (render_view->GetEnabledBindings() & BINDINGS_POLICY_WEB_UI) && (frame_url.SchemeIs(kChromeUIScheme) || frame_url.SchemeIs(url::kDataScheme));

        if (!webui_enabled)
            return false;

        *frame_ptr = frame;
        *render_view_ptr = render_view;
        return true;
    }

} // namespace

// Exposes two methods:
//  - chrome.send: Used to send messages to the browser. Requires the message
//      name as the first argument and can have an optional second argument that
//      should be an array.
//  - chrome.getVariableValue: Returns value for the input variable name if such
//      a value was set by the browser. Else will return an empty string.
void WebUIExtension::Install(blink::WebFrame* frame)
{
    v8::Isolate* isolate = blink::mainThreadIsolate();
    v8::HandleScope handle_scope(isolate);
    v8::Local<v8::Context> context = frame->mainWorldScriptContext();
    if (context.IsEmpty())
        return;

    v8::Context::Scope context_scope(context);

    v8::Local<v8::Object> chrome = GetOrCreateChromeObject(isolate,
        context->Global());
    chrome->Set(gin::StringToSymbol(isolate, "send"),
        gin::CreateFunctionTemplate(
            isolate, base::Bind(&WebUIExtension::Send))
            ->GetFunction());
    chrome->Set(gin::StringToSymbol(isolate, "getVariableValue"),
        gin::CreateFunctionTemplate(
            isolate, base::Bind(&WebUIExtension::GetVariableValue))
            ->GetFunction());
}

// static
void WebUIExtension::Send(gin::Arguments* args)
{
    blink::WebFrame* frame;
    RenderView* render_view;
    if (!ShouldRespondToRequest(&frame, &render_view))
        return;

    std::string message;
    if (!args->GetNext(&message)) {
        args->ThrowError();
        return;
    }

    if (base::EndsWith(message, "RequiringGesture",
            base::CompareCase::SENSITIVE)
        && !blink::WebUserGestureIndicator::isProcessingUserGesture()) {
        NOTREACHED();
        return;
    }

    // If they've provided an optional message parameter, convert that into a
    // Value to send to the browser process.
    std::unique_ptr<base::ListValue> content;
    if (args->PeekNext().IsEmpty() || args->PeekNext()->IsUndefined()) {
        content.reset(new base::ListValue());
    } else {
        v8::Local<v8::Object> obj;
        if (!args->GetNext(&obj)) {
            args->ThrowError();
            return;
        }

        std::unique_ptr<V8ValueConverter> converter(V8ValueConverter::create());
        content = base::ListValue::From(
            converter->FromV8Value(obj, frame->mainWorldScriptContext()));
        DCHECK(content);
    }

    // Send the message up to the browser.
    render_view->Send(new ViewHostMsg_WebUISend(render_view->GetRoutingID(),
        frame->document().url(),
        message,
        *content));
}

// static
std::string WebUIExtension::GetVariableValue(const std::string& name)
{
    blink::WebFrame* frame;
    RenderView* render_view;
    if (!ShouldRespondToRequest(&frame, &render_view))
        return std::string();

    return WebUIExtensionData::Get(render_view)->GetValue(name);
}

} // namespace content
